//----------------------------------------------------------------
// CS 564 fall 2012
// Project Stage 4 
// Instructor: Anhai Doan
// Date: 11/05/2012
//
// Group Member:
// Name: Yuye Wang ID: 9062548244 Login: yuye
// Name: Yao Liu ID: 9038315546 Login: yao
// Name: Cong Zhao ID: 9066589277 Login: czhao
//
// Project Description:
// In this project we implemented a file manager for heap files that 
// provides a scan mechanisum that allow us to search a heap file for 
// records that satisfy a search predicated. The heapfile layer imposes
// a logical ordering on the pages via a linked list, while the files
// at DB layer are physical stored.
//----------------------------------------------------------------

#include "heapfile.h"
#include "error.h"

//-----------------------------------------------------------------
// This function creates an empty heap file, and initialize the 
// header page, first page and last page. It inputs fileName as a 
// string. It returns OK if succeed, FILEEXISTS if the file heapfile 
// already exists and other error messages if error happens. 
//-----------------------------------------------------------------

// routine to create a heapfile
const Status createHeapFile(const string fileName)
{
    File* 		file;
    Status 		status;
    FileHdrPage*	hdrPage;
    int			hdrPageNo;
    int			newPageNo;
    Page*		newPage;

    // try to open the file. This should return an error
    status = db.openFile(fileName, file);
    if (status != OK)
    {
		// file doesn't exist. First create it and allocate
		// an empty header page and data page.
		status = db.createFile(fileName);
		if (status != OK) return status;

		status = db.openFile(fileName, file);
		if (status != OK) return status;

		Page* hdrPagePointer;
		status = bufMgr->allocPage(file, hdrPageNo, hdrPagePointer);
		if (status != OK) return status;

		hdrPage = (FileHdrPage*)hdrPagePointer;
		// initialize some fields in the header page.		
		memcpy( hdrPage->fileName, fileName.c_str(), fileName.size() );
		hdrPage->pageCnt = 1;
		hdrPage->recCnt = 0;
		// create a new page for the first data page.
		status = bufMgr->allocPage(file, newPageNo, newPage);
		if (status != OK) return status;

		newPage->init(newPageNo);
		// initialize the firstPage and lastPage field in the header page.
		hdrPage->firstPage = newPageNo;
		hdrPage->lastPage = newPageNo;

		// unpin both pages
		status = bufMgr->unPinPage(file, hdrPageNo, true);
		if (status != OK) return status;

		status = bufMgr->unPinPage(file, newPageNo, true);
		if (status != OK) return status;

		// flush both pages to disk
		//status = bufMgr->flushFile(file);
		//if (status != OK) return status;
		// close the file
		status = db.closeFile(file);
		return (OK);
    }
    return (FILEEXISTS);
}
// routine to destroy a heapfile
const Status destroyHeapFile(const string fileName)
{
	return (db.destroyFile (fileName));
}


//-----------------------------------------------------------------
// This is the constructor of the Heap file which construct a page 
// by passing in the fileName. returnStatus is set to OK upon success.
//-----------------------------------------------------------------
// constructor opens the underlying file
HeapFile::HeapFile(const string & fileName, Status& returnStatus)
{
    Status 	status;
    Page*	pagePtr;

    //    cout << "opening file " << fileName << endl;

    // open the file and read in the header page and the first data page
    if ((status = db.openFile(fileName, filePtr)) == OK)
    {
		// get the header page number
		status = filePtr->getFirstPage(headerPageNo);
		if (status != OK) {returnStatus = status; return;}

		// read and pin the header page for the file in the buffer pool
		// initialize data members
		status = bufMgr->readPage(filePtr, headerPageNo, pagePtr);
		if (status != OK) {returnStatus = status; return;}

		hdrDirtyFlag = false;
		headerPage = (FileHdrPage*) pagePtr;
		// read and pin the first page of the file into the buffer pool
		status = bufMgr->readPage(filePtr, headerPage->firstPage, pagePtr);
		if (status != OK) {returnStatus = status; return;}
		// initialize the curPage, curPageNo, curDirtyFlag. Set curRec to NULLRID
		curPage = pagePtr;
		curPageNo = headerPage->firstPage;
		curDirtyFlag = false;
		curRec = NULLRID;

		returnStatus = status;
		return;
    }
    else
    {
    	cerr << "open of heap file failed\n";
		returnStatus = status;
		return;
    }
}

// the destructor closes the file
HeapFile::~HeapFile()
{
    Status status;
    //    cout << "invoking heapfile destructor on file " << headerPage->fileName << endl;

    // see if there is a pinned data page. If so, unpin it 
    if (curPage != NULL)
    {
    	status = bufMgr->unPinPage(filePtr, curPageNo, curDirtyFlag);
		curPage = NULL;
		curPageNo = 0;
		curDirtyFlag = false;
		if (status != OK) cerr << "error in unpin of date page\n";
    }
	
	 // unpin the header page
    status = bufMgr->unPinPage(filePtr, headerPageNo, hdrDirtyFlag);
    if (status != OK) cerr << "error in unpin of header page\n";
	
	// status = bufMgr->flushFile(filePtr);  // make sure all pages of the file are flushed to disk
	// if (status != OK) cerr << "error in flushFile call\n";
	// before close the file
	status = db.closeFile(filePtr);
    if (status != OK)
    {
		cerr << "error in closefile call\n";
		Error e;
		e.print (status);
    }
}

// Return number of records in heap file

const int HeapFile::getRecCnt() const
{
  return headerPage->recCnt;
}

//-----------------------------------------------------------------
// retrieve an arbitrary record from a file.
// if record is not on the currently pinned page, the current page
// is unpinned and the required page is read into the buffer pool
// and pinned. Returns a pointer to the record via the rec parameter
//-----------------------------------------------------------------

const Status HeapFile::getRecord(const RID & rid, Record & rec)
{
    Status status;

    //    cout<< "getRecord. record (" << rid.pageNo << "." << rid.slotNo << ")" << endl;
    
    // check if the record is in current page
    if (rid.pageNo == curPageNo) {
	status = curPage->getRecord(rid, rec);
	return status;
    } else {
	// the record is not in current page, need to move to another page
	// unpin current page
	status = bufMgr->unPinPage(filePtr, curPageNo, curDirtyFlag);
	if (status != OK) return status;
	// read the pageNo in RID into buffer pool
	status = bufMgr->readPage(filePtr, rid.pageNo, curPage);
	if (status != OK) return status;
	// now get the record from the current page	
	status = curPage->getRecord(rid, rec);
	if (status != OK) return status;
	// set the data members accordingly
	curPageNo = rid.pageNo;
	curDirtyFlag = false;
	curRec = rid;
	status = OK;
    }
    return status;
}

HeapFileScan::HeapFileScan(const string & name,
			   Status & status) : HeapFile(name, status)
{
    filter = NULL;
}

const Status HeapFileScan::startScan(const int offset_,
				     const int length_,
				     const Datatype type_, 
				     const char* filter_,
				     const Operator op_)
{
    if (!filter_) {                        // no filtering requested
        filter = NULL;
        return OK;
    }
    
    if ((offset_ < 0 || length_ < 1) ||
        (type_ != STRING && type_ != INTEGER && type_ != FLOAT) ||
        (type_ == INTEGER && length_ != sizeof(int)
         || type_ == FLOAT && length_ != sizeof(float)) ||
        (op_ != LT && op_ != LTE && op_ != EQ && op_ != GTE && op_ != GT && op_ != NE))
    {
        return BADSCANPARM;
    }

    offset = offset_;
    length = length_;
    type = type_;
    filter = filter_;
    op = op_;

    return OK;
}


const Status HeapFileScan::endScan()
{
    Status status;
    // generally must unpin last page of the scan
    if (curPage != NULL)
    {
        status = bufMgr->unPinPage(filePtr, curPageNo, curDirtyFlag);
        curPage = NULL;
        curPageNo = 0;
		curDirtyFlag = false;
        return status;
    }
    return OK;
}

HeapFileScan::~HeapFileScan()
{
    endScan();
}

const Status HeapFileScan::markScan()
{
    // make a snapshot of the state of the scan
    markedPageNo = curPageNo;
    markedRec = curRec;
    return OK;
}

const Status HeapFileScan::resetScan()
{
    Status status;
    if (markedPageNo != curPageNo) 
    {
		if (curPage != NULL)
		{
			status = bufMgr->unPinPage(filePtr, curPageNo, curDirtyFlag);
			if (status != OK) return status;
		}
		// restore curPageNo and curRec values
		curPageNo = markedPageNo;
		curRec = markedRec;
		// then read the page
		status = bufMgr->readPage(filePtr, curPageNo, curPage);
		if (status != OK) return status;
		curDirtyFlag = false; // it will be clean
    }
    else curRec = markedRec;
    return OK;
}


//-----------------------------------------------------------------
// This function finds the next record that satisfies the scan predicate. 
// If the next record satisfies the condition, return the rid of the page, 
// if not, go to the next record. Return OK if success. 
//-----------------------------------------------------------------

const Status HeapFileScan::scanNext(RID& outRid)
{
    Status 	status = OK;
    RID		nextRid;
    RID		tmpRid;
    int 	nextPageNo;
    Record      rec;
    Page*       pagePtr;

    // if the curRec is not initiallized, make it the first record on page
    if (curRec.pageNo == -1)
      {
	if (curPageNo == headerPage->firstPage)
	  status = curPage->firstRecord(nextRid);
	// if it's in lastPage terminate it
	else if (curPageNo == headerPage->lastPage)
	  return FILEEOF;
      }
    else{
      tmpRid = curRec;
      status = curPage->nextRecord(tmpRid, nextRid);
    }
    
    while( true ){
      // if end of page, unpinn the page, load next page, update all the iterators 
      // pin the new page start on the new page.
      if (status != OK)  
	{
	  if (status == ENDOFPAGE || status == NORECORDS) 
	    {
	      status = bufMgr->unPinPage(filePtr, curPageNo, curDirtyFlag);
	      if (status != OK) return status;
	      status = curPage->getNextPage(nextPageNo);
	      if (status != OK) return status;
	      // if no next page exist, it's end of file
	      if (nextPageNo == -1)
		{
		  //curRec = NULLRID;
		  return FILEEOF;
		}
	      // if there is a next page 
	      curPageNo = nextPageNo;
	      // load next page and pin it
	      status = bufMgr->readPage(filePtr, nextPageNo, pagePtr);
	      if (status != OK) return status;
	      curPage = pagePtr;
	      curDirtyFlag = false;
	      // read the first record on new page
	      status = curPage->firstRecord(nextRid);
	      // check whether status is OK or not that's there's a record in new page
	      continue;
	    }
	  else
	    return status; // return other errors if exist
	}
      
      // convert rid into a Record, rec, to the record
      status = curPage->getRecord(nextRid, rec);
      if (status != OK) return status;
      // check whether the nextRid matches what requested
      if (matchRec(rec) == true) {
	curRec = nextRid; 
	status = OK;
	outRid = curRec;
	return status;
      }else
	{
	  tmpRid = nextRid;
	  status = curPage->nextRecord(tmpRid, nextRid);	
	}
    }	
}

// returns pointer to the current record.  page is left pinned
// and the scan logic is required to unpin the page 

const Status HeapFileScan::getRecord(Record & rec)
{
    return curPage->getRecord(curRec, rec);
}

// delete record from file. 
const Status HeapFileScan::deleteRecord()
{
    Status status;

    // delete the "current" record from the page
    status = curPage->deleteRecord(curRec);
    curDirtyFlag = true;

    // reduce count of number of records in the file
    headerPage->recCnt--;
    hdrDirtyFlag = true; 
    return status;
}


// mark current page of scan dirty
const Status HeapFileScan::markDirty()
{
    curDirtyFlag = true;
    return OK;
}

const bool HeapFileScan::matchRec(const Record & rec) const
{
    // no filtering requested
    if (!filter) return true;

    // see if offset + length is beyond end of record
    // maybe this should be an error???
    if ((offset + length -1 ) >= rec.length)
	return false;

    float diff = 0;                       // < 0 if attr < fltr
    switch(type) {

    case INTEGER:
        int iattr, ifltr;                 // word-alignment problem possible
        memcpy(&iattr,
               (char *)rec.data + offset,
               length);
        memcpy(&ifltr,
               filter,
               length);
        diff = iattr - ifltr;
        break;

    case FLOAT:
        float fattr, ffltr;               // word-alignment problem possible
        memcpy(&fattr,
               (char *)rec.data + offset,
               length);
        memcpy(&ffltr,
               filter,
               length);
        diff = fattr - ffltr;
        break;

    case STRING:
        diff = strncmp((char *)rec.data + offset,
                       filter,
                       length);
        break;
    }

    switch(op) {
    case LT:  if (diff < 0.0) return true; break;
    case LTE: if (diff <= 0.0) return true; break;
    case EQ:  if (diff == 0.0) return true; break;
    case GTE: if (diff >= 0.0) return true; break;
    case GT:  if (diff > 0.0) return true; break;
    case NE:  if (diff != 0.0) return true; break;
    }

    return false;
}

InsertFileScan::InsertFileScan(const string & name,
                               Status & status) : HeapFile(name, status)
{
  //Do nothing. Heapfile constructor will bread the header page and the first
  // data page of the file into the buffer pool
}

InsertFileScan::~InsertFileScan()
{
    Status status;
    // unpin last page of the scan
    if (curPage != NULL)
    {
        status = bufMgr->unPinPage(filePtr, curPageNo, true);
        curPage = NULL;
        curPageNo = 0;
        if (status != OK) cerr << "error in unpin of data page\n";
    }
}


//-----------------------------------------------------------------
// This function insert a record to the heap file, by passing in the
// Record rec. It returns the rid of the inserted record. The function 
// returns OK if the insertion succeeds.
//-----------------------------------------------------------------

const Status InsertFileScan::insertRecord(const Record & rec, RID& outRid)
{
    Page*	newPage;
    int		newPageNo;
    Status	status, unpinstatus;
    RID		rid;

    // check for very large records
    if ((unsigned int) rec.length > PAGESIZE-DPFIXED)
    {
        // will never fit on a page, so don't even bother looking
        return INVALIDRECLEN;
    }
    //if current page is null, read the last page from disk
    if (curPage == NULL){
	curPageNo = headerPage->lastPage;
	status = bufMgr->readPage(filePtr, curPageNo, curPage);
	if (status != OK) return status;
    }

    // check if current page is the last page. If not, read in last page.
    if (curPageNo != headerPage->lastPage) {
	unpinstatus = bufMgr->unPinPage(filePtr, curPageNo, curDirtyFlag);
	if (unpinstatus != OK) return unpinstatus;

        status = bufMgr->readPage(filePtr, headerPage->lastPage, curPage);
	if (status != OK) return status;
        // set the data members accordingly
	curPageNo = headerPage->lastPage;
	curDirtyFlag = false;
	curRec = NULLRID;
    }
  
    // try insert the record.
    status = curPage->insertRecord(rec, rid);
    // If not success, create a new page.
    if ( status != OK) { 
	// unpin current page	
	unpinstatus = bufMgr->unPinPage(filePtr, curPageNo, curDirtyFlag);
	if (unpinstatus != OK) return unpinstatus;
	
	// allocate a new page	
	status = bufMgr->allocPage(filePtr, newPageNo, newPage);
	if (status != OK) return status;

	// initialize the new page
	newPage->init(newPageNo);

	// update header page
	headerPage->lastPage = newPageNo;
	headerPage->pageCnt ++ ;
	hdrDirtyFlag = true;
	// set the data members accordingly
	curPage->setNextPage(newPageNo);
	curPage = newPage;	
	curPageNo = newPageNo;
	curDirtyFlag = false;
	curRec = NULLRID;
	//insert
	status = curPage->insertRecord(rec, rid);
	if (status != OK) return status;

    }

	// now current page has empty space. 
    	curDirtyFlag = true;
    	outRid = rid;

    	// update header page
    	headerPage->recCnt ++ ;
    	hdrDirtyFlag = true;
    	return status;

   
  
    
}


